package sensimulator.intersection.lanes;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.ImageObserver;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import sennetworkserver.LaneStore;
import sennetworkserver.Sensor;
import sensimulator.intersection.Drawable;
import sensimulator.intersection.Intersection;
import sensimulator.intersection.Painter;
import sensimulator.intersection.traffic.TrafficUser;

/**
 * Represents a single lane.
 * @author Erwin
 *
 */
public abstract class Lane implements Observer, Drawable {
	
	protected Vector<TrafficUser> traffic;
	
	protected List<TrafficLightNode> trafficLights;
	
	protected int laneId;
	
	protected Intersection intersection;
	
	protected Lane followUp;
	
	private LaneNode firstNode;
	
	private LaneNode lastNode;
	
	public Lane(int laneId, Intersection intersection) {
		this(laneId, intersection, true);
	}
	
	public Lane(int laneId, Intersection intersection, boolean receivesUpdates) {
		traffic = new Vector<TrafficUser>();
		trafficLights = new ArrayList<TrafficLightNode>();
		this.laneId = laneId;
		this.intersection = intersection;
		
		if (receivesUpdates) {
			sennetworkserver.Lane networkLane = LaneStore.getLane(laneId);
			networkLane.getSensor100m().addObserver(this);
		}
		
		intersection.addDrawable(this);
	}
	
	@Override
	public void update(Observable o, Object arg) {
		if (arg == null || (Boolean) arg)
			spawnTraffic(false);
	}
	
	public void tick(long time, int delta) {

		synchronized(traffic) {
			for (Iterator<TrafficUser> iterator = traffic.iterator(); iterator.hasNext(); ) {
				TrafficUser user = iterator.next();
				user.tick(time, delta);
				
				// Remove cars that are disposed from the list
				if (user.isDisposed())
					iterator.remove();
			}
		}
		
	}

	public abstract void spawnTraffic(boolean triggerSensor);
	
	protected void addTraffic(TrafficUser user) {
		traffic.add(user);
		intersection.addDrawable(user);
	}
	
	public void addNode(Point point, boolean isTrafficLight) {
		Point nodePoint = new Point(point.x, point.y);
	
		if (firstNode == null)
			lastNode = firstNode = new LaneNode(this, nodePoint);
		else {
			lastNode.addNext(nodePoint, isTrafficLight);
			lastNode = lastNode.getNext();
		}
	}
	
	public void addTrafficLight(Point point) { addTrafficLight(point, 0); }
	
	public void addTrafficLight(Point point, int rotation) {
		trafficLights.add(new TrafficLightNode(new Point(point.x, point.y), rotation));
	}

	public abstract void paint(Graphics g, ImageObserver imageObserver);
	
	public Lane clone(int laneId, int rotation) throws Exception {
		return clone(laneId, rotation, true);
	}
	
	public Lane clone(int laneId, int rotation, boolean receivesUpdates) throws Exception {
		Class<? extends Lane> type = this.getClass();
		Constructor<? extends Lane> c = type.getConstructor(int.class, Intersection.class, boolean.class);
		
		try { 
			Lane newLane = c.newInstance(laneId, intersection, receivesUpdates);
			
			LaneNode node = firstNode;
			Point origin = new Point(Intersection.WIDTH / 2, Intersection.HEIGHT / 2);
			while (node != null) {
				Point newPoint = rotateAround(node.getPoint(), origin, rotation);
				
				newLane.addNode(newPoint, node.isTrafficLight());
				node = node.getNext();
			}
			
			for (TrafficLightNode light : trafficLights)
				newLane.addTrafficLight(rotateAround(light.getPoint(), origin, rotation), rotation);
			
			return newLane;
		} 
		catch (SecurityException e) { e.printStackTrace(); return null; } 
	}
	
	public boolean isDisposed() { return false; }
	
	public int getZOrder() { return Painter.TOP_LAYER; }
	
	public int getId() { return laneId; }
	
	public LaneNode getFirstNode() { return firstNode; }
	
	public LaneNode getLastNode() { return lastNode; }

	public Vector<TrafficUser> getTraffic() { return traffic; }
	
	public Lane getFollowUp() { return followUp; }
	
	public void setFollowUp(Lane followUp) { this.followUp = followUp; }
	
	static Point rotateAround(Point point, Point origin, float rotation) {
		return new Point((int) (origin.x + Math.cos(Math.toRadians(rotation)) * (point.x - origin.x) + Math.sin(Math.toRadians(rotation)) * (point.y - origin.y)),
				         (int) (origin.y - Math.sin(Math.toRadians(rotation)) * (point.x - origin.x) + Math.cos(Math.toRadians(rotation)) * (point.y - origin.y)));
	}

	
}
